// Copyright (c) 1996-2002 John Lyon-Smith. All rights reserved.

#pragma once

#include <wxtl\Base.h>
#include <wxtl\Event.h>
#include <wxtl\FileMap.h>
#include <wxtl\Interlock.h>

namespace wxtl
{
	TCHAR const kszMetSectFileMapObjFmt[] = _T("wxtl.MeteredSection.FileMap.%s");
	TCHAR const kszMetSectEventObjFmt[] = _T("wxtl.MeteredSection.Event.%s");

	// TODO: Allow access to current available count, threads waiting, max. count

	class MeteredSection
	{
	public:
		MeteredSection() {}
		~MeteredSection() 
		{
			close();
		}

		// Shared info needed for metered section
		class MeteredSectionShared : public FileMap
		{
			friend MeteredSection;
		
		public:	
			MeteredSectionShared()
			{
				fInitialized = false;
				lSpinLock = 0;
				lThreadsWaiting = 0;
				lAvailableCount = 0;
				lMaximumCount = 0;
			}
			
			MeteredSectionShared* data() { return (MeteredSectionShared*)FileMap::data(); }
		
		protected:	
			BOOL   fInitialized;     // Is the metered section initialized?
			LONG   lSpinLock;        // Used to gain access to this structure
			LONG   lThreadsWaiting;  // Count of threads waiting
			LONG   lAvailableCount;  // Available resource count
			LONG   lMaximumCount;    // Maximum resource count
		};

		// Interface functions
		bool create(const TCHAR* pszName = NULL, LONG lInitialCount = 1, LONG lMaximumCount = 1)
		{
			// Verify the parameters
			if (lMaximumCount < 1 ||
				lInitialCount > lMaximumCount ||
				lInitialCount < 0)
			{
				return false;
			}

			const TCHAR* psz = NULL;
			TCHAR sz[MAX_PATH];
			
			if (pszName != NULL)
			{
				_stprintf_s(sz,MAX_PATH, kszMetSectEventObjFmt, pszName);
				psz = sz;
			}

			if (!m_evt.create(false, true, psz))
			{
				return false;
			}

			if (pszName != NULL)
			{
				_stprintf_s(sz,MAX_PATH, kszMetSectFileMapObjFmt, pszName);
				psz = sz;
			}
			
			bool bOpened = false;

			if (!m_shared.create(sizeof(MeteredSectionShared), &bOpened, psz))
			{
				m_evt.close();
				return false;
			}

			MeteredSectionShared* pShared = m_shared.data();

			if (!bOpened)
			{
				pShared->lSpinLock = 0;
				pShared->lThreadsWaiting = 0;
				pShared->lAvailableCount = lInitialCount;
				pShared->lMaximumCount = lMaximumCount;
				wxtl::Exchange<BOOL>(&(pShared->fInitialized), true);
			}
			else
			{   
				// Already exists; wait for it to be initialized by the creator
				while (!pShared->fInitialized) Sleep(0);
			}
			
			return true;
		}

		bool open(const TCHAR* pszName)
		{
			if (pszName == NULL)
			{
				return false;
			}

			const TCHAR* psz = NULL;
			TCHAR sz[MAX_PATH];
			
			if (pszName != NULL)
			{
				_stprintf_s(sz,MAX_PATH, kszMetSectEventObjFmt, pszName);
				psz = sz;
			}

			if (!m_evt.open(psz))
			{
				return false;
			}

			if (pszName != NULL)
			{
				_stprintf_s(sz,MAX_PATH, kszMetSectFileMapObjFmt, pszName);
				psz = sz;
			}

			if (!m_shared.open(psz))
			{
				m_evt.close();
				return false;
			}

			return true;
		}

		void close()
		{
			m_evt.close();
			m_shared.close();
		}

		bool tryEnter(DWORD dwMilliseconds = INFINITE, bool* pbTimeout = NULL)
		{
			MeteredSectionShared* pShared = m_shared.data();
				
			if (pbTimeout)
				*pbTimeout = false;
			
			if (!pShared)
				return false;
				
			for (;;)
			{
				getLock(pShared);

				// We have access to the metered section, everything we do now will be atomic
				if (pShared->lAvailableCount >= 1)
				{
					pShared->lAvailableCount--;
					releaseLock(pShared);
					return true;
				}
				else
				{
					// Couldn't get in. Wait on the event object
					pShared->lThreadsWaiting++;
					
					m_evt.reset();
					releaseLock(pShared);
					
					if (!m_evt.wait(dwMilliseconds))
					{
						if (pbTimeout)
							*pbTimeout = true;
						return false;
					}
				}
			}
		}

		bool leave(LONG lReleaseCount = 1, LPLONG pPreviousCount = NULL)
		{
			MeteredSectionShared* pShared = m_shared.data();
			
			if (!pShared)
				return false;
				
			int iCount;
			
			getLock(pShared);

			// Save the old value if they want it
			if (pPreviousCount)
			{
				*pPreviousCount = pShared->lAvailableCount;
			}

			// We have access to the metered section, everything we do now will be atomic
			if ((lReleaseCount < 0) ||
				(pShared->lAvailableCount + lReleaseCount > pShared->lMaximumCount))
			{
				releaseLock(pShared);
				return false;
			}
			pShared->lAvailableCount += lReleaseCount;
		    
			// Set the event the appropriate number of times
			lReleaseCount = min(lReleaseCount, pShared->lThreadsWaiting);
			
			if (pShared->lThreadsWaiting)
			{
				for (iCount=0; iCount < lReleaseCount ; iCount++)
				{
					pShared->lThreadsWaiting--;
					m_evt.set();
				}
			}
			
			releaseLock(pShared);
			
			return true;
		}

	private:
		void getLock(MeteredSectionShared* pShared)
		{
			// Spin and get access to the metered section lock
			while (wxtl::Exchange<LONG>(&(pShared->lSpinLock), 1) != 0)
				Sleep(0);
		}

		void releaseLock(MeteredSectionShared* pShared)
		{
			wxtl::Exchange<LONG>(&(pShared->lSpinLock), 0);
		}

	private:
		Event m_evt;
		MeteredSectionShared m_shared;
	};
}
